﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ServiceProcess;
using System.ServiceModel;
using System.Threading;
using nwrole.lib;

namespace nwrole.service
{
    static internal class DeployUtil
    {
        #region Services
        
        public static string ServiceIdentifierFormat = "jazz.integration.services.hosted.{0}";

        public static bool InstallService (nwrole.lib.Solution.Service service)
        {
            if (ServiceInstalled(service.Id))
                return false;
            EventLogger.LogInformation("DeployUtil.InstallService", String.Format("Installing service '{0}'.", service.Id));
            service.ManagementPort = 0;
            service.ManagementPort = NextFreeManagementPort();
            ServiceUtil.InstallService(Path.Combine(Configuration.RootDirectoryPath, SupervisorService.ApplicationExe) + " integration " + service.Id.ToString("N") + " " + service.ManagementPort.ToString(),
                ServiceIdentifier(service.Id),
                service.Name,
                service.Username,
                service.Password);
            EventLogger.LogInformation("DeployUtil.InstallService", String.Format("Installed service '{0}'.", service.Id));
            return true;
        }

        public static bool UninstallService (Guid serviceId)
        {
            if (!ServiceInstalled(serviceId))
                return false;
            EventLogger.LogInformation("DeployUtil.UninstallService", String.Format("Uninstalling service '{0}'.", serviceId));
            ServiceUtil.UninstallService(ServiceIdentifier(serviceId));
            EventLogger.LogInformation("DeployUtil.UninstallService", String.Format("Uninstalled service '{0}'.", serviceId));
            return true;
        }

        public static bool StartService (Guid serviceId)
        {
            if (!ServiceInstalled(serviceId) || ServiceRunning(serviceId))
                return false;
            ServiceController ctrl = new ServiceController(ServiceIdentifier(serviceId));
            try
            {
                ctrl.Start();
                ctrl.WaitForStatus(ServiceControllerStatus.Running);
            }
            catch (Exception exception) {
                EventLogger.LogException("DeployUtil.StartService", exception);
                return false;
            }
            finally { ctrl.Dispose(); }
            return true;
        }

        public static bool StopService (Guid serviceId)
        {
            if (!ServiceInstalled(serviceId) || !ServiceRunning(serviceId))
                return false;
            ServiceController ctrl = new ServiceController(ServiceIdentifier(serviceId));
            try
            {
                ctrl.Stop();
                ctrl.WaitForStatus(ServiceControllerStatus.Stopped);
            }
            catch (Exception exception)
            {
                EventLogger.LogException("DeployUtil.StopService", exception);
                return false; 
            }
            finally { ctrl.Dispose(); }
            return true;
        }

        public static bool ServiceInstalled (Guid id)
        {
            ServiceController ctrl = new ServiceController(ServiceIdentifier(id));
            try { var status = ctrl.Status; }
            catch (Exception exception) {
                EventLogger.LogException("DeployUtil.ServiceInstalled", exception);
                return false;
            }
            finally { ctrl.Dispose(); }
            return true;
        }

        public static bool ServiceRunning (Guid id)
        {
            if (!ServiceInstalled(id))
                return false;
            ServiceController ctrl = new ServiceController(ServiceIdentifier(id));
            try { return (ctrl.Status == ServiceControllerStatus.Running); }
            catch (Exception exception) {
                EventLogger.LogException("DeployUtil.ServiceInstalled", exception);
                return false; 
            }
            finally { ctrl.Dispose(); }
        }

        public static bool ServiceManagerRunning (nwrole.lib.Solution.Service service)
        {
            ServiceManagement mgr = null;
            try
            {
                mgr = new ServiceManagement(service);
                if (mgr.GetIdentifier().ToString("N") != service.Id.ToString("N"))
                    return false;
            }
            catch (Exception exception)
            {
                EventLogger.LogException("DeployUtil.ServiceManagerRunning", exception);
                return false;
            }
            finally
            {
                if (mgr != null)
                    mgr.Dispose();
            }
            return true;
        }

        public static string ServiceIdentifier (Guid id)
        {
            return String.Format(ServiceIdentifierFormat, id.ToString("N"));
        }

        public static int NextFreeManagementPort ()
        {
            int port = 63000;
            bool success = true;
            do
            {
                port++;
                success = true;
                foreach (var service in Configuration.Services)
                {
                    if (service.ManagementPort == port)
                    {
                        success = false;
                        break;
                    }
                }
            } while (!success);
            return port;
        }
        #endregion

        #region Solutions
        public static Result DeploySolution (Guid solutionId, bool forceDeployment)
        {
            Solution solution = Configuration.SolutionWithId(solutionId);

            if (solution == null)
            {
                EventLogger.LogError("DeployUtil.DeploySolution", String.Format("Solution '{0}' does not exist.", solutionId));
                return Result.SolutionIdUnavailable;
            }

            if (!forceDeployment && solution.State == Solution.SolutionState.Deployed)
            {
                EventLogger.LogError("DeployUtil.DeploySolution", String.Format("Solution '{0}' is already deployed", solutionId));
                return Result.SolutionAlreadyDeployed;
            }

            foreach (var service in Configuration.ServicesInSolution(solutionId))
            {
                if (DeployUtil.ServiceInstalled(service.Id))
                {
                    if (DeployUtil.ServiceRunning(service.Id))
                        DeployUtil.StopService(service.Id);
                    DeployUtil.UninstallService(service.Id);
                }

                DeployUtil.InstallService(service);
                service.State = Solution.Service.ServiceState.Activated;
                DeployUtil.StartService(service.Id);
                service.State = Solution.Service.ServiceState.Running;

                //Thread.Sleep(TimeSpan.FromSeconds(3));

                if (!DeployUtil.ServiceManagerRunning(service))
                {
                    // reset everything?
                    throw new Exception("Service manager not running!");
                }
                
                foreach (var instance in Configuration.InstancesInService(service.Id))
                {
                    //using (ServiceManagement mgr = new ServiceManagement(service))
                    ServiceManagement mgr = new ServiceManagement(service);
                    {
                        mgr.InstantiatePlugin(Configuration.PluginWithId(instance.PluginId),
                            solution,
                            service,
                            instance);
                    }
                    instance.State = Solution.Instance.InstanceState.Running;
                    mgr.Dispose();
                    Configuration.PluginWithId(instance.PluginId).Instances.Add(new Plugin.PluginInstance { InstanceId = instance.Id, ServiceId = service.Id, SolutionId = solution.Id });
                }
            }

            solution.Deployed = DateTime.Now;
            solution.State = Solution.SolutionState.Deployed;
            Configuration.Save();

            return Result.Success;
        }

        public static Result RetractSolution (Guid solutionId, bool forceRetraction)
        {
            Solution solution = Configuration.SolutionWithId(solutionId);

            if (solution == null)
            {
                EventLogger.LogError("DeployUtil.RetractSolution", String.Format("Solution '{0}' does not exist.", solutionId));
                return Result.SolutionIdUnavailable;
            }

            if (solution.State == Solution.SolutionState.Retracted)
            {
                EventLogger.LogError("DeployUtil.RetractSolution", String.Format("Solution '{0}' is already retracted", solutionId));
                return Result.SolutionAlreadyRetracted;
            }

            foreach (var service in Configuration.ServicesInSolution(solutionId))
            {
                if (service.State == Solution.Service.ServiceState.Running)
                {
                    DeployUtil.StopService(service.Id);
                    service.State = Solution.Service.ServiceState.Activated;
                }
                if (service.State != Solution.Service.ServiceState.Deactivated)
                {
                    DeployUtil.UninstallService(service.Id);
                    service.State = Solution.Service.ServiceState.Deactivated;
                }

                foreach (var instance in Configuration.InstancesInService(service.Id))
                {
                    instance.State = Solution.Instance.InstanceState.Stopped;
                    var i = (from a in Configuration.PluginWithId(instance.PluginId).Instances where a.InstanceId == instance.Id select a).FirstOrDefault();
                    if (i != null)
                        Configuration.PluginWithId(instance.PluginId).Instances.Remove(i);
                }
            }

            if (!forceRetraction)
            {
                solution.Deployed = null;
                solution.State = Solution.SolutionState.Retracted;
            }
            Configuration.Save();

            return Result.Success;
        }
        #endregion
    }
}
